﻿using Opc.Ua;
using Opc.Ua.Server;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace OPCControlUA
{
    internal class NodeManagerFactory : INodeManagerFactory
    {
        public NodeManager? NodeManager { get; private set; }
        public StringCollection NamespacesUris => ["http://opcfoundation.org/OpcUaServer"];
        public INodeManager Create(IServerInternal server, Opc.Ua.ApplicationConfiguration configuration)
        {
            if (NodeManager != null)
                return NodeManager;
            NodeManager = new NodeManager(server, configuration, [.. NamespacesUris]);
            return NodeManager;
        }
    }
    /**
     需继承CustomNodeManager2，这是OPC UA标准库中提供的类。
    */
    internal class NodeManager : CustomNodeManager2
    {
        public NodeManager(IServerInternal server, params string[] namespaceUris)
            : base(server, namespaceUris)
        {
        }
        public NodeManager(IServerInternal server, Opc.Ua.ApplicationConfiguration configuration, params string[] namespaceUris)
            : base(server, configuration, namespaceUris)
        {
        }
        //重写LoadPredefinedNodes方法，在该方法中配置预定义节点。其中创建了一个Root文件夹，Root文件夹中添加了Test变量。
        protected override NodeStateCollection LoadPredefinedNodes(ISystemContext context)
        {
            FolderState root = CreateFolder(null, null, "Root");
            //root.AddReference(ReferenceTypes.Organizes, true, ObjectIds.ObjectsFolder)该语句将节点添加到OPC UA服务器根节点，如果不使用该语句，可在Server节点下看到添加的节点。
            root.AddReference(ReferenceTypes.Organizes, true, ObjectIds.ObjectsFolder); // 将节点添加到服务器根节点
            root.EventNotifier = EventNotifiers.SubscribeToEvents;
            AddRootNotifier(root);
            CreateVariable(root, null, "Test", BuiltInType.Int64, ValueRanks.Scalar);
            //**实验，用完删除
            var testState = TestAddNodState();
            return new NodeStateCollection([testState, root]);
        }

        //**实验，用完删除
        FolderState TestAddNodState() {
            var root2 = new FolderState(null)
            {
                //SymbolicName = "s_MyTestRoot",
                ReferenceTypeId = ReferenceTypes.Organizes,
                TypeDefinitionId = ObjectTypeIds.FolderType,
                NodeId = new NodeId("nid_MyTestRoot", NamespaceIndex), //必须项，id带上父级， 必须指定NamespaceIndex
                BrowseName = new QualifiedName("b_MyTestRoot", NamespaceIndex), //名称不用带父级，详细属性里显示
                DisplayName = new LocalizedText("en", "d_MyTestRoot"),//名称不用带父级，目录树用该值
            };
            var level2 = new FolderState(root2)
            {
                //SymbolicName = "s_MyFolderLevel2",
                TypeDefinitionId = ObjectTypeIds.FolderType,
                NodeId = new NodeId("nid_level2", NamespaceIndex),
                BrowseName = new QualifiedName("My_MyTestRoot", NamespaceIndex),
                DisplayName = new LocalizedText("en", "d_MyFolderLevel2"),
            };
            root2.AddChild(level2); //必须调用
            var var2 = new BaseDataVariableState(level2)
            {
                SymbolicName = "s_MyTestVariable",
                Value = 123456,
                TypeDefinitionId = VariableTypeIds.BaseDataVariableType,
                NodeId = new NodeId("nid_var2", NamespaceIndex),
                BrowseName = new QualifiedName("b_My_My_My_MyTestRoot", NamespaceIndex),
                DisplayName = new LocalizedText("en", "d_MyTestVariable"),
            };
            level2.AddChild(var2); //必须调用

            root2.AddReference(ReferenceTypes.Organizes, true, ObjectIds.ObjectsFolder); // 将节点添加到服务器根节点，必须调用
            root2.EventNotifier = EventNotifiers.SubscribeToEvents;  // 浏览时非必须
            AddRootNotifier(root2); // 浏览时非必须
            return root2;
        }

        //CreateFolder是自定义的方法，用于简化创建文件夹节点。
        protected virtual FolderState CreateFolder(NodeState? parent, string? path, string name)
        {
            if (string.IsNullOrWhiteSpace(path))
                path = parent?.NodeId.Identifier is string id ? id + "_" + name : name;
            FolderState folder = new(parent)
            {
                SymbolicName = name,
                ReferenceTypeId = ReferenceTypes.Organizes,
                TypeDefinitionId = ObjectTypeIds.FolderType,
                NodeId = new NodeId(path, NamespaceIndex),
                BrowseName = new QualifiedName(path, NamespaceIndex),
                DisplayName = new LocalizedText("en", name),
                WriteMask = AttributeWriteMask.None,
                UserWriteMask = AttributeWriteMask.None,
                EventNotifier = EventNotifiers.None
            };
            parent?.AddChild(folder);
            return folder;
        }
        //CreateVariable是自定义的方法，用于简化创建变量节点。
        protected virtual BaseDataVariableState CreateVariable(NodeState? parent, string? path, string name, BuiltInType dataType, int valueRank)
        {
            return CreateVariable(parent, path, name, (uint)dataType, valueRank);
        }
        protected virtual BaseDataVariableState CreateVariable(NodeState? parent, string? path, string name, NodeId dataType, int valueRank)
        {
            if (string.IsNullOrWhiteSpace(path))
                path = parent?.NodeId.Identifier is string id ? id + "_" + name : name;
            BaseDataVariableState variable = new(parent)
            {
                SymbolicName = name,
                ReferenceTypeId = ReferenceTypes.Organizes,
                TypeDefinitionId = VariableTypeIds.BaseDataVariableType,
                NodeId = new NodeId(path, NamespaceIndex),
                BrowseName = new QualifiedName(path, NamespaceIndex),
                DisplayName = new LocalizedText("en", name),
                WriteMask = AttributeWriteMask.None,
                UserWriteMask = AttributeWriteMask.None,
                DataType = dataType,
                ValueRank = valueRank,
                AccessLevel = AccessLevels.CurrentReadOrWrite,
                UserAccessLevel = AccessLevels.CurrentReadOrWrite,
                Historizing = false,
                Value = Opc.Ua.TypeInfo.GetDefaultValue(dataType, valueRank, Server.TypeTree),
                StatusCode = StatusCodes.Good,
                Timestamp = DateTime.UtcNow
            };
            if (valueRank == ValueRanks.OneDimension)
            {
                variable.ArrayDimensions = new ReadOnlyList<uint>(new List<uint> { 0 });
            }
            else if (valueRank == ValueRanks.TwoDimensions)
            {
                variable.ArrayDimensions = new ReadOnlyList<uint>(new List<uint> { 0, 0 });
            }
            parent?.AddChild(variable);
            return variable;
        }
        //UpdateValue是用于更新变量节点值的方法。其中修改值后，需调用ClearChangeMasks方法，才能通知客户端更新值。
        public void UpdateValue(NodeId nodeId, object value)
        {
            var variable = (BaseDataVariableState)FindPredefinedNode(nodeId, typeof(BaseDataVariableState));
            if (variable != null)
            {
                variable.Value = value;
                variable.Timestamp = DateTime.UtcNow;
                variable.ClearChangeMasks(SystemContext, false);
            }
        }
        //AddFolder用于启动服务器后添加新的文件夹。
        public NodeId AddFolder(NodeId parentId, string? path, string name)
        {
            var node = Find(parentId);
            var newNode = CreateFolder(node, path, name);
            AddPredefinedNode(SystemContext, node);
            return newNode.NodeId;
        }
        //AddVariable用于启动服务器后添加新的变量。
        public NodeId AddVariable(NodeId parentId, string? path, string name, BuiltInType dataType, int valueRank)
        {
            return AddVariable(parentId, path, name, (uint)dataType, valueRank);
        }
        public NodeId AddVariable(NodeId parentId, string? path, string name, NodeId dataType, int valueRank)
        {
            var node = Find(parentId);
            var newNode = CreateVariable(node, path, name, dataType, valueRank);
            AddPredefinedNode(SystemContext, node);
            return newNode.NodeId;
        }
    }
}
